#include <stdint.h>
#include <stdio.h>
#include "flash.h"
#include "bootloader.h"

typedef  void (*pFunction)(void);

/*
GD32F303RCT6 flash memoru details:
bootloader size:12KB 0x08000000-0x08002FFF
applicationA size:250KB 0x08003000-0x080417FF
applicationB size:250KB 0x08041800-0x0807FFFF

user data size: 2KB 0x087F800-0x807FFFF
upgrade available flag address: 0x0807F800, 4 bytes, 0x5A5A5A5A:available, 0xFFFFFFFF:unavailable

*/


#define UPGRADE_FLAG_AVAILABLE 0x5A5A5A5A
#define UPGRADE_FLAG_UNAVAILABLE 0xFFFFFFFF
#define BOOTLOADER_SIZE 0x3000

//app size: 56KB bytes
#define APPLICATION_SIZE 0xE000
//page size: 2KB bytes
#define PAGE_SIZE 0x800
//app page number: 56KB/2KB = 28
#define APP_PAGE_NUMBER 28

#define FLASH_BASE_ADDRESS 0x08000000

#define APPLICATIONA_START_ADDRESS (FLASH_BASE_ADDRESS + BOOTLOADER_SIZE)
#define APPLICATIONB_START_ADDRESS (APPLICATIONA_START_ADDRESS + APPLICATION_SIZE)
#define USER_DATA_START_ADDRESS (APPLICATIONB_START_ADDRESS + APPLICATION_SIZE)

#define UPGRADE_FLAG_ADDRESS USER_DATA_START_ADDRESS

void BootLoaderPoll(void)
{
    pFunction Jump_To_Application;
    uint32_t JumpAddress = 0;

    #if 1
    //check if the upgrade flag is available
    if(*(uint32_t*)UPGRADE_FLAG_ADDRESS == UPGRADE_FLAG_AVAILABLE)
    {
        printf("upgrade flag is available\r\n");

        //check if the applicationB is valid
        if(0x20000000 == (*(uint32_t*)APPLICATIONB_START_ADDRESS & 0x2FFE0000))
        {
            //start copy applicationB to applicationA
            uint32_t *pSrc = (uint32_t*)APPLICATIONB_START_ADDRESS;

            // printf("read applicationB\r\n");
            // uint32_t *pSrc_temp = pSrc;
            // for(uint32_t i = 0; i < APP_PAGE_NUMBER; i++)
            // {
            //     for(uint32_t j = 0; j < PAGE_SIZE/4; j++)
            //     {
            //         if(0 == ((APPLICATIONB_START_ADDRESS+i*PAGE_SIZE + j*4)%PAGE_SIZE))
            //         {
            //             printf("\r\npage 0x%08X:\r\n",APPLICATIONB_START_ADDRESS+i*PAGE_SIZE + j*4);
            //         }

            //         printf("0x%08X ",*pSrc_temp);
            //         pSrc_temp++;
            //     }
            // }

            printf("fmc unlock\r\n");
            //flash unlock
            fmc_unlock();

            printf("start copy applicationB to applicationA\r\n");

            printf("erase applicationA area\r\n first\r\n");
            //erase applicationA
            for(uint32_t i = 0; i < APP_PAGE_NUMBER; i++)
            {
                fmc_page_erase(APPLICATIONA_START_ADDRESS + i*PAGE_SIZE);
            }

            fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR|FMC_FLAG_PGAERR|FMC_FLAG_PGERR);
            //copy applicationB to applicationA
            for(uint32_t i = 0; i < APP_PAGE_NUMBER; i++)
            {
                for(uint32_t j = 0; j < PAGE_SIZE/4; j++)
                {
                    // printf("write data:0x%08X\r\n",*pSrc);
                    if(0 != flash_write_word(APPLICATIONA_START_ADDRESS + i*PAGE_SIZE + j*4, *pSrc++))
                    {
                        printf("flash write error\r\n");
                        while(1);
                    }
                 
                    fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR|FMC_FLAG_PGAERR|FMC_FLAG_PGERR);
                }
                printf("page 0x%08X write success\r\n",APPLICATIONA_START_ADDRESS + i*PAGE_SIZE);
            }

            //flash lock
            printf("fmc lock\r\n");
            fmc_lock();
        }
        else
        {
            printf("applicationB is invalid\r\n");
            return;
            // while(1);
        }
    }
    else
    {
        printf("upgrade flag is unavailable,flag value is:0x%08X\r\n",*(uint32_t*)UPGRADE_FLAG_ADDRESS);
    }
    #endif

    #if 0
    printf("\r\n\r\n\r\read applicationA\r\n");
    uint32_t *pSrc_temp2 = (uint32_t*)APPLICATIONA_START_ADDRESS;
    for(uint32_t i = 0; i < APP_PAGE_NUMBER; i++)
    {
        for(uint32_t j = 0; j < PAGE_SIZE/4; j++)
        {
            if(0 == ((APPLICATIONA_START_ADDRESS+i*PAGE_SIZE + j*4)%PAGE_SIZE))
            {
                printf("\r\npage 0x%08X:\r\n",APPLICATIONA_START_ADDRESS+i*PAGE_SIZE + j*4);
            }

            printf("0x%08X ",*pSrc_temp2);
            pSrc_temp2++;
        }
    }
    #endif

    // printf("check if the applicationA is valid\r\n");
    //check if the applicationA is valid
    if(0x20000000 == (*(__IO uint32_t*)APPLICATIONA_START_ADDRESS & 0x2FFE0000))
    {
        #if 0
        printf("compare applicationA and applicationB\r\n");
        for(uint32_t i = 0; i < APP_PAGE_NUMBER; i++)
        {
            for(uint32_t j = 0; j < PAGE_SIZE/4; j++)
            {
                if(*((uint32_t*)(APPLICATIONA_START_ADDRESS + i*PAGE_SIZE + j*4)) != *((uint32_t*)(APPLICATIONB_START_ADDRESS + i*PAGE_SIZE + j*4)))
                {
                    printf("found applicationA and applicationB different place:\r\n");
                    printf("applicationA address:0x%08X, data:0x%08X\r\n",APPLICATIONA_START_ADDRESS + i*PAGE_SIZE + j*4,*((uint32_t*)(APPLICATIONA_START_ADDRESS + i*PAGE_SIZE + j*4)));    
                    printf("applicationB address:0x%08X, data:0x%08X\r\n",APPLICATIONB_START_ADDRESS + i*PAGE_SIZE + j*4,*((uint32_t*)(APPLICATIONB_START_ADDRESS + i*PAGE_SIZE + j*4)));
                    printf("\r\n\r\n");
                }
            }
        }
        #endif

        printf("\r\napplicationA is valid and jump to applicationA.........\r\n");
        //jump to applicationA
        __disable_irq(); //disable all interrupts
        usart_disable(USART1);
        dma_channel_disable(DMA_CH2);
        dma_channel_disable(DMA_CH3);
        JumpAddress = *(__IO uint32_t*) (APPLICATIONA_START_ADDRESS + 4);
        Jump_To_Application = (pFunction) JumpAddress; 
        /** 设置主堆栈指针 */
        __set_MSP(*(__IO uint32_t*) APPLICATIONA_START_ADDRESS);                        //
        Jump_To_Application();  
    }
    else
    {
        printf("applicationA is invalid\r\n");
        // while(1);
    }
}


int SimulateUpgrade(void)
{
    if(0x20000000 != (*(__IO uint32_t*)APPLICATIONA_START_ADDRESS & 0x2FFE0000))
    {
        uint32_t temp = *(uint32_t*)(UPGRADE_FLAG_ADDRESS+4);

        fmc_unlock();
        fmc_page_erase(UPGRADE_FLAG_ADDRESS);
        fmc_word_program(UPGRADE_FLAG_ADDRESS,UPGRADE_FLAG_AVAILABLE);
        fmc_word_program(UPGRADE_FLAG_ADDRESS+4,temp);
        fmc_lock();

        if(*(uint32_t*)UPGRADE_FLAG_ADDRESS != UPGRADE_FLAG_AVAILABLE)
        {
            printf("upgrade flag write fail,readback value is:0x%08X\r\n",(*(uint32_t*)UPGRADE_FLAG_ADDRESS));
            return -1;
        }
    }

    // printf("upgrade flag clear success\r\n");

    return 0;
}



